Создание расширений
===================

Поскольку создание расширений подразумевает их использование сторонними разработчиками,
процесс создания требует дополнительных усилий. Ниже приведены основные правила, которые необходимо соблюдать
при создании расширений:

* расширение должно быть самодостаточным, т.е. зависимость от внешних ресурсов должна быть минимальна. Очень неудобно, когда для работы
расширения требуется устанавливать дополнительные пакеты, классы и иные файлы ресурсов;
* все файлы расширения должны быть собраны в одной директории, имя которой должно совпадать с названием расширения;
* классы расширения должны начинаться с префикса, чтобы избежать конфликтов имён с классами других расширений;
* расширение должно включать подробную документацию по его установке и API, чтобы сократить время, затрачиваемое другими разработчиками на его подключение и изучение;
* расширение должно использовать надлежащую лицензию. Если вы хотите, чтобы ваше расширение могло быть использовано как открытыми, так и закрытыми проектами,
вы можете воспользоваться лицензиями BSD, MIT и др., но не GPL, т.к. последняя требует, чтобы код, в котором используется ваше расширение, также был открыт.

Ниже мы расскажем о том, как создать новое расширение в соответствии с классификацией,
приведённой в [обзоре](/doc/guide/extension.overview). Пояснения в равной степени распространяются и на
расширения, используемые исключительно в собственных проектах.

Компонент приложения
---------------------

[Компонент приложения](/doc/guide/basics.application#application-component) должен реализовывать интерфейс [IApplicationComponent]
или расширять класс [CApplicationComponent]. Основной метод, который необходимо реализовать, — [IApplicationComponent::init].
В этом методе происходит инициализация компонента. Метод вызывается после того, как компонент создан и установлены начальные значения,
указанные в [конфигурации приложения](/doc/guide/basics.application#application-configuration).

По умолчанию компонент приложения создаётся и инициализируется только в момент первого обращения к нему в ходе обработки запроса.
Если необходимо принудительно создавать компонент сразу после создания экземпляра приложения, то нужно добавить идентификатор
этого компонента в свойство [CApplication::preload].

Поведение
---------

Для того чтобы создать поведение, необходимо реализовать интерфейс [IBehavior].
Для удобства в Yii имеется класс [CBehavior], реализующий этот интерфейс и
предоставляющий некоторые общие методы. Наследуемые классы могут реализовать
дополнительные методы, которые будут доступны в компонентах, к которым прикреплено
поведение.

При разработке поведений для [CModel] и [CActiveRecord] можно наследовать классы
[CModelBehavior] и [CActiveRecordBehavior] соответственно. Эти базовые классы
предоставляют дополнительные возможности, специально созданные для [CModel] и [CActiveRecord].
К примеру, класс [CActiveRecordBehavior] реализует набор методов для обработки
событий жизненного цикла ActiveRecord. Наследуемый класс, таким образом, может
перекрыть эти методы и выполнить код, участвующий в жизненном цикле AR.

Следующий код демонстрирует пример поведения ActiveRecord. Если это поведение
прикреплено к объекту AR и вызван метод `save()`, атрибутам `create_time` и
`update_time` будет автоматически присвоено текущее время.

~~~
[php]
class TimestampBehavior extends CActiveRecordBehavior
{
	public function beforeSave($event)
	{
		if($this->owner->isNewRecord)
			$this->owner->create_time=time();
		else
			$this->owner->update_time=time();
	}
}
~~~

Виджет
------

[Виджет](/doc/guide/basics.view#widget) должен расширять класс [CWidget] или производные от него.

Наиболее простой способ создать виджет — расширить существующий виджет и переопределить его методы или заменить значения по умолчанию.
Например, если вы хотите заменить CSS стиль для [CTabView], то, используя виджет, нужно установить свойство [CTabView::cssFile]. Можно также расширить класс
[CTabView], чтобы при использовании виджета не требовалась постоянная конфигурация, следующим образом:

~~~
[php]
class MyTabView extends CTabView
{
	public function init()
	{
		if($this->cssFile===null)
		{
			$file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css';
			$this->cssFile=Yii::app()->getAssetManager()->publish($file);
		}
		parent::init();
	}
}
~~~

Выше мы переопределяем метод [CWidget::init] и, если свойство [CTabView::cssFile] не установлено,
присваиваем ему значение URL нового CSS стиля по умолчанию. Файл нового CSS стиля необходимо поместить
в одну папку с файлом класса `MyTabView`, чтобы их можно было упаковать как расширение. Поскольку CSS стиль не доступен из веб, его
необходимо опубликовать как ресурс.

Чтобы написать виджет с нуля, нужно, как правило, реализовать два метода: [CWidget::init] и [CWidget::run].
Первый метод вызывается, когда мы используем конструкцию `$this->beginWidget` для вставки виджета в представление,
а второй — когда используется конструкция `$this->endWidget`.
Если необходимо получить и обработать содержимое, заключённое между вызовами этих двух функций,
то можно запустить [буферизацию вывода](http://php.net/manual/en/book.outcontrol.php) в
[CWidget::init] и получать сохранённый вывод для дальнейшей обработки в методе [CWidget::run].

Когда виджет используется на странице, он обычно подключает CSS стили, JavaScript файлы и прочие файлы ресурсов.
Файлы такого рода называются *ресурсы*, поскольку они хранятся вместе с файлом класса виджета и, как правило, не доступны веб-пользователям.
Для того чтобы они стали доступны пользователям, их необходимо опубликовать, используя [CWebApplication::assetManager], как показано во фрагменте кода выше.
Помимо этого, если необходимо подключить файлы CSS стилей или JavaScript на текущей странице, их необходимо зарегистрировать посредством [CClientScript]:

~~~
[php]
class MyWidget extends CWidget
{
	protected function registerClientScript()
	{
		// …подключаем здесь файлы CSS или JavaScript…
		$cs=Yii::app()->clientScript;
		$cs->registerCssFile($cssFile);
		$cs->registerScriptFile($jsFile);
	}
}
~~~

Виджет также может иметь собственные файлы представлений. В этом случае необходимо создать директорию `views`
внутри директории, содержащей файл класса виджета, и поместить в неё все файлы представлений.
Для подключения представлений виджета в его классе используется конструкция `$this->render('ViewName')`, аналогичная соответствующему методу контроллера.

Действие
------

[Действие](/doc/guide/basics.controller#action) должно расширять класс [CAction] или производные от него. [IAction::run] — основной метод, который необходимо
реализовать для действия.

Фильтр
------

Фильтр должен расширять класс [CFilter] или производные от него. Основными методами, которые необходимо реализовать,
являются [CFilter::preFilter] и [CFilter::postFilter]. Первый вызывается до выполнения действия, второй — после.

~~~
[php]
class MyFilter extends CFilter
{
	protected function preFilter($filterChain)
	{
		// применяется до выполнения действия
		return true; // значение false возвращается, если действие не должно выполняться
	}

	protected function postFilter($filterChain)
	{
		// применяется после завершения выполнения действия
	}
}
~~~

Параметр `$filterChain` — экземпляр класса [CFilterChain], содержащий информацию о действии, к которому применяются фильтры в настоящий момент.


Контроллер
----------

[Контроллер](/doc/guide/basics.controller), распространяемый как расширение, должен наследовать класс [CExtController], а не класс [CController].
Основной причиной этого является то, что для класса [CController] предполагается, что файлы представлений располагаются
в `application.views.ControllerID`, а для [CExtController] считается, что файлы представлений находятся в директории `views`,
расположенной в той же директории, что и файл класса этого контроллера. Очевидно, что расширение-контроллер удобнее распространять, когда все
файлы расширения собраны в одном месте.

Валидатор
---------

Валидатор должен расширять класс [CValidator] и реализовывать его метод [CValidator::validateAttribute].

~~~
[php]
class MyValidator extends CValidator
{
	protected function validateAttribute($model,$attribute)
	{
		$value=$model->$attribute;
		if($value has error)
			$model->addError($attribute,$errorMessage);
	}
}
~~~

Команда консоли
---------------

[Консольная команда](/doc/guide/topics.console) должна расширять класс [CConsoleCommand]
и реализовывать его метод [CConsoleCommand::run]. При желании можно переопределить
метод [CConsoleCommand::getHelp], который отвечает за информационную справку по команде.

~~~
[php]
class MyCommand extends CConsoleCommand
{
	public function run($args)
	{
		// $args — массив аргументов, переданных с командой
	}

	public function getHelp()
	{
		return 'Usage: how to use this command';
	}
}
~~~

Модуль
------
Информация о порядке создания и использования модулей представлена в разделе [Модуль](/doc/guide/basics.module#using-module).

Если сформулировать требования в общем виде, то модуль должен быть самодостаточным, файлы ресурсов (CSS, JavaScript, изображения), используемые модулем, должны распространяться вместе с модулем, а сам модуль  должен публиковать ресурсы, чтобы они были доступны для веб-пользователей.


Компонент общего вида
-----------------

Разработка компонента общего вида аналогична написанию класса. Компонент, как и модуль,
должен быть самодостаточен и удобен для использования другими разработчиками.